package gui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Toolkit;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.ScrollPaneConstants;

import algorithms.GroverCircuit;
import register.Register;

/**
 * Creates and manages the UI.
 * @author Matt Cole
 */
public class UserInterface {
	
	/** Width of users desktop */
	private int screenWidth;
	/** Height of users desktop */
	private int screenHeight;
	/** Store the grover circuit object */
	private GroverCircuit circuit;
	
	// Components of the UI that can be manipulated after the widow is first drawn
	/** Introduction fame object */
	private JFrame introFrame;
	/** Number of qubits field in into frame */
	private JTextField numQubitsField;
	/** Searched element field in into frame */
	private JTextField searchField;
	/** Main program frame */
	private JFrame mainFrame;
	/** Data text area displaying program info */
	private JTextArea textArea;
	/** Panel showing the rotation of states in vector diagram */
	private VectorPlot vectorPlot;
	/** Panel of the probabilities of the qubits */
	private ProbPlot probPlot;
	/** Panel showing options, like functional vs matrix representation of gates */
	private OptionsPanel optionsPanel;
	/** Take measurement button */
	private JButton measureButton;
	/** Next step in program button */
	private JButton nextStepButton;
	
	/** Constructor for the UI.
	 * 
	 * @param circuit Grover circuit to display UI for.
	 */
	public UserInterface(GroverCircuit circuit) {
		
		this.circuit = circuit;
		
		// Get the dimensions of the screen - used to centre the windows
		Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
		screenWidth = (int)d.getWidth();
		screenHeight = (int)d.getHeight();
		
		createIntro();
		createMain();
		
	}
	
	/**
	 * Creates the simple intro window used to get the user inputs.
	 */
	public void createIntro() {
		
		// Create the intro frame
		introFrame = new JFrame("Quantum Computer");
		introFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		introFrame.setResizable(false);

		// Create button panel
		JButton nextButton = new JButton("Next");
		nextButton.addActionListener(circuit);
		nextButton.setActionCommand("next");
		JButton exitButton = new JButton("Exit");
		exitButton.addActionListener(circuit);
		exitButton.setActionCommand("exit");
		JPanel buttonPanel = new JPanel();
		buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 20, 20, 20));
		buttonPanel.add(nextButton);
		buttonPanel.add(exitButton);
		
		// Create label panel
		int min = circuit.getMinNumQubits();
		int max = circuit.getMaxNumQubits();
		JLabel numQubitsLabel = new JLabel("Enter n - the number of qubits in the register (between " + min + " and " + max + "):");
		JLabel searchLabel = new JLabel("Enter the search element (between 0 and (2^n)-1):");
		JPanel labelPanel = new JPanel(new GridLayout(0,1));
		labelPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 0));
		labelPanel.add(numQubitsLabel);
		labelPanel.add(searchLabel);

		// Create text field panel
		numQubitsField = new JTextField(10);
		searchField = new JTextField(10);
		JPanel fieldPanel = new JPanel(new GridLayout(0,1));
		fieldPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
		fieldPanel.add(numQubitsField);
		fieldPanel.add(searchField);

		// Combine the text field and label panel
		numQubitsLabel.setLabelFor(numQubitsField);
		searchLabel.setLabelFor(searchField);
		JPanel inputPanel = new JPanel(new BorderLayout());
		inputPanel.add(labelPanel, BorderLayout.CENTER);
		inputPanel.add(fieldPanel, BorderLayout.LINE_END);
		
		// Add buttons panel and input panel to the frame
		introFrame.getContentPane().add(inputPanel, BorderLayout.CENTER);
		introFrame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
		
		// Calculate the position
		introFrame.pack();
		int width = introFrame.getWidth();
		int height = introFrame.getHeight();
		int x = (screenWidth - width)/2;
		int y = (screenHeight- height)/2;
		introFrame.setLocation(x, y);
		
	}
	
	/**
	 * Creates the main program window in which to display the information about the
	 * state of the register.
	 */
	public void createMain() {
		
		// Create the main frame
		mainFrame = new JFrame("Quantum Computer");
		mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mainFrame.getContentPane().setLayout(new BorderLayout());
		
		// Create the plots
		vectorPlot = new VectorPlot(screenWidth/2);
		probPlot = new ProbPlot(screenWidth/2);
		
		// Create the options panel
		optionsPanel = new OptionsPanel();
		
		// Create the data text area
		textArea = new JTextArea(9, 50);
		textArea.setEditable(false);
		JScrollPane dataPanel = new JScrollPane(textArea,
				ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		dataPanel.setBackground(Color.white);
		dataPanel.setBorder(BorderFactory.createTitledBorder("Data"));
		int dataPanelWidth = screenWidth - optionsPanel.getWidthAndHeight();
		int dataPanelHeight = optionsPanel.getWidthAndHeight();
		dataPanel.setPreferredSize(new Dimension(dataPanelWidth, dataPanelHeight));
		
		// Combine data text area and options panel
		JPanel titlePanel = new JPanel(new BorderLayout());
		titlePanel.add(dataPanel, BorderLayout.LINE_START);
		titlePanel.add(optionsPanel, BorderLayout.LINE_END);
		
		// Create the buttons panel
		nextStepButton = new JButton("Next Step");
		nextStepButton.addActionListener(circuit);
		nextStepButton.setActionCommand("next_step");
		JButton exitButton = new JButton("Exit");
		exitButton.addActionListener(circuit);
		exitButton.setActionCommand("exit");
		measureButton = new JButton("Measure");
		measureButton.addActionListener(circuit);
		measureButton.setActionCommand("measure");
		JButton resetButton = new JButton("Reset");
		resetButton.addActionListener(circuit);
		resetButton.setActionCommand("reset");
		JButton newButton = new JButton("New Simulation");
		newButton.addActionListener(circuit);
		newButton.setActionCommand("new");
		JPanel buttonPanel = new JPanel();
		buttonPanel.add(newButton);
		buttonPanel.add(resetButton);
		buttonPanel.add(nextStepButton);
		buttonPanel.add(measureButton);
		buttonPanel.add(exitButton);
		buttonPanel.setBackground(Color.white);
		
		// Add the components to frame
		mainFrame.getContentPane().add(probPlot, BorderLayout.LINE_START);
		mainFrame.getContentPane().add(vectorPlot, BorderLayout.LINE_END);
		mainFrame.getContentPane().add(titlePanel, BorderLayout.PAGE_START);
		mainFrame.getContentPane().add(buttonPanel, BorderLayout.PAGE_END);
		
		// Set the size of the frame
		mainFrame.pack();
		mainFrame.setExtendedState(Frame.MAXIMIZED_BOTH);
		
	}

	/**
	 * Displays the intro window, clearing and disabling (but not hiding) the main window.
	 */
	public void showIntro() {
		resetMain();
		mainFrame.setEnabled(false);
		numQubitsField.setText("");
		searchField.setText("");
		numQubitsField.requestFocusInWindow();
		introFrame.setVisible(true);
	}
	
	/**
	 * Initial setup of the main window for a given register and search element, hiding the intro window.
	 * @param reg
	 * @param searchElem
	 */
	public void showMain(Register reg, int searchElem) {
		resetMain();
		introFrame.setVisible(false);
		probPlot.setup(reg);
		vectorPlot.setup(reg, searchElem);
		nextStepButton.setEnabled(true);
		measureButton.setEnabled(false);
		mainFrame.setEnabled(true);
		mainFrame.setVisible(true);
	}
	
	/**
	 * Clears all components of the main frame.
	 */
	public void resetMain() {
		probPlot.reset();
		vectorPlot.reset();
		optionsPanel.reset();
		textArea.setText("");
	}

	/**
	 * Updates the display of the main window for a given register.
	 * @param reg The register for which the display needs to be updated.
	 * @param action String to be printed to the UI.
	 * @param updateVectorPlot If true, the vector plot will also be updated when this method is called.
	 */
	public void update(Register reg, String action, Boolean updateVectorPlot) {
		textArea.append(action+"\n");
		probPlot.update(reg);
		if(updateVectorPlot) {
			vectorPlot.update();
		}
	}
	
	/**
	 * Removes the vector plot from the UI. For use when the register is measured.
	 */
	public void removeRegStateVector() {
		vectorPlot.removeRegState();
	}
	
	/**
	 * Enables the measure button. Used once the register has been prepared.
	 */
	public void enableMeasureButton() {
		measureButton.setEnabled(true);
	}
	
	/**
	 * Disables the step buttons on the UI. For use after a measurement is taken.
	 */
	public void disableStepButtons() {
		measureButton.setEnabled(false);
		nextStepButton.setEnabled(false);
	}
	
	/**
	 * Enables the next step and measure buttons on the UI.
	 */
	public void enableStepButtons() {
		measureButton.setEnabled(true);
		nextStepButton.setEnabled(true);
	}

	/**
	 * Uses JDialog to display an error message.
	 * @param msg Error message to be displayed.
	 */
	public void inputError(String msg) {
		JOptionPane.showMessageDialog(introFrame, msg, "Error", JOptionPane.ERROR_MESSAGE);
	}
	
	/**
	 * @return The contents of the reg size text field.
	 */
	public String getNumQubits() {
		return numQubitsField.getText();
	}
	
	/**
	 * @return The contents of the search text field.
	 */
	public String getSearchElem() {
		return searchField.getText();
	}

	/**
	 * Displays a message in the data area of the main display.
	 * @param msg The message to be displayed.
	 */
	public void printMessage(String msg) {
		textArea.append(msg+"\n");
	}

}
